//
// Created by 86183 on 2022/11/22.
//

/* 数据的存储 */

#include <stdio.h>
#include <string.h>
#include <float.h>

void test()
{
    printf("hehe\n");
}

int check_sys()
{
    int a = 1;
    // char* p = (char*) &a
    //返回 1，小端
    //返回 0，大端
    return *((char*) &a);
}

int main()
{
    //数据类型
    // 1.内置类型 - char, short, int ...
    // 2.自定义类型(构造类型)
    {
        int a = 10;// 4 个字节 - 0a 00 00 00
        float f = 10.0F;// 4 个字节 - 00 00 20 41
        //一样吗？
        getchar();
        //类型的意义：
        // 1.使用这个类型开辟的内存空间的大小(大小决定了使用范围)
        // 2.如何看待内存空间的视角
    }
    //整型家族：
    // char - ASCII 码值
    // short
    // int
    // long
    //每个类型都分 unsigned / signed，有符号位的整型的最高位是符号位，0 代表正数，1 代表负数

    //浮点型家族：
    // float
    // double

    //构造类型：
    //数组类型
    //结构体类型 - struct
    //枚举类型 - enum
    //联合类型 - union

    //指针类型：
    // int*
    // char*
    // float*
    // void*

    //空类型 - void

    test(100);
    //函数定义()中不加 void 的时候可以写数字，加上会报错
    {
        int a = 20;// 14 00 00 00
        int b = -10;// f6 ff ff ff
    }
    //整型有符号数 - 原反补(无符号数也有但是三种相同)
    //有符号数的正数原反补相同
    //原码 - 直接翻译成 32 bit 位的二进制数字，以 20 为例
    // 00000000000000000000000000010100 - 原码
    // 00000000000000000000000000010100 - 反码
    // 00000000000000000000000000010100 - 补码
    //补码转换成 16 进制 - 00 00 00 14
    //反码 - 原码的符号位不变，其他位按位取反
    //补码 - 反码 + 1，以 -10 为例
    // 10000000000000000000000000001010 - 原码
    // 11111111111111111111111111110101 - 反码
    // 11111111111111111111111111110110 - 补码
    //补码转换成 16 进制 - ff ff ff f6
    //内存存放的是数据的补码，可以将正数和负数进行统一运算

    //大小端介绍
    //大端(字节序存储)模式：数据的低位保存在内存的高地址中，而数据的高位，保存在内存的低地址中
    //小端(字节序存储)模式：数据的低位保存在内存的低地址中，而数据的高位，保存在内存的高地址中
    //例：11 22 33 44 - 44 是低位，11 是高位
    //存储在数据中：11 22 33 44 的模式 - 44 在数据中属于高地址 - 大端存储模式
    // 44 33 22 11 的模式 - 11 在数据中属于高地址 - 小端存储模式
    // Clion 使用的是小端存储模式
    {
        //写一段代码，判断程序的存储方式是大端存储还是小端存储
        int a = 20;// 0x00 00 00 14 - 判断第一个字节是 00 还是 14
        char* p = (char*) &a;
        printf("%d\n", *p == 20);// 1，说明是小端

        //设计一个函数实现这个功能 - 返回 1 小端，0 大端
        int ret = check_sys();
        if (ret == 1)
        {
            printf("小端\n");
        }
        else
        {
            printf("大端\n");
        }
    }
    {
        //小练习 - 输出结果是？
        char a = -1;
        signed char b = -1;
        unsigned char c = -1;
        printf("a = %d b = %d c = %d\n", a, b, c);// -1 -1 255
        // -1 的补码是 32 个 1
        //放入 a, b, c 后发生截断，变成 8 个 1
        //打印 %d - 整型提升 - 有符号位的按符号位提升，无符号位的补 0
        // a 和 b 是全 1，c 的前 24 位是 0
    }
    {
        //小练习 - 输出结果是？
        char a = -128;
        printf("%u\n", a);// 4294967168
        // -128 的二进制序列是 10000000000000000000000010000000
        //补码是 11111111111111111111111110000000
        //截断之后整型提升 - 和原来的补码一样
        //打印无符号数 - 补码 == 原码 - 很大的数
    }
    // signed char - -128 ~ 127
    // 10000000 - 这个补码被直接识别为 -128，因为没法借位
    // unsigned char - 0 ~ 255
    // 00000001 ··· 1
    // 00000010 ··· 2
    // 00000011 ··· 3
    // ...
    // 01111110 ··· 126
    // 01111111 ··· 127
    // 10000000 ··· -128 = 127 + 1
    // 10000001 ··· -127
    // ...
    // 11111110 ··· -2
    // 11111111 ··· -1

    {
        //小练习 - 输出结果是？
        char a = 128;// 127 + 1 = -128
        printf("%u\n", a);//同 -128
    }
    {
        //小练习 - 输出结果是？
        int i = -20;
        unsigned int j = 10;
        printf("%d\n", i + j);// -10
        // -20 的原码 10000000000000000000000000010100
        //补码 11111111111111111111111111101100
        // 10 的原码 00000000000000000000000000001010
        //补码 00000000000000000000000000001010
        //相加 11111111111111111111111111110110 - 补码 - -10
    }
    {
        //小练习 - 输出结果是？
        /*
        unsigned int i;
        for (i = 9; i >= 0; i --)
        {
            printf("%u\n", i);
        }
        */
        //死循环 9 8 7 6 5 4 3 2 1 0 4294967295 ...
    }
    {
        char a[1000];
        int i;
        for (i = 0; i < 1000; i ++)
        {
            a[i] = -1 - i;
        }
        printf("%d\n", strlen(a));// 255
        // 111111111111111111111111 11111111 - -1
        // 111111111111111111111111 11111110 - -2
        // ...
        // 111111111111111111111111 10000001 - -127
        // 111111111111111111111111 10000000 - -128
        // 111111111111111111111111 01111111 - 127
        // 111111111111111111111111 01111110 - 126
        // ...
        // 111111111111111111111111 00000001 - 1
        // 111111111111111111111111 00000000 - 0 - '\0' - 结束
        // strlen 不包括 '\0' - 1 ~ 127 + (-1) ~ -128 = 255
    }
    {
        //小练习 - 输出结果是？
        /*
        unsigned char i = 0;
        for (i = 0; i <= 255; i ++)
        {
            printf("hello world\n");
        }
        */
        //死循环打印 hello world
        // 255 + 1 = 0
    }
    //总结：无符号数容易发生死循环

    //浮点型在内存中的存储
    {
        double d = 1E10;// 1 * 10^10
        printf("%lf\n", d);
    }

    //浮点数的取值范围 - float.h
    printf("%lf\n", DBL_MAX);
    printf("%lf\n", DBL_MIN);
    printf("%lf\n", FLT_MAX);
    printf("%lf\n", FLT_MIN);

    {
        int n = 9;
        float* pFloat = (float*) &n;
        printf("n 的值为：%d\n", n);// 9
        printf("pFloat 的值为：%f\n", *pFloat);// 0.000000

        *pFloat = 9.0F;
        printf("n 的值为：%d\n", n);// 1091567616
        printf("pFloat 的值为：%f\n", *pFloat);// 9.000000
    }
    //说明：整型在内存中的存储方式和浮点型不同

    //浮点数的存储方式
    //根据 IEEE754 标准，任意一个二进制浮点数 V 可以表示成以下形式:
    // V = (-1)^S * M * 2^E
    // (-1)^S 表示符号位，当 S = 0，V 为正数；S = 1，V 为负数
    // M 表示有效数字，大于等于 1，小于 2
    // 2^E 表示指数位

    //举例：9.0 --> 1001.0 --> (-1)^0 * 1.001 * 2^3
    // S = 0, M = 1.001, E = 3

    // IEEE754 规定，对于 32 位的浮点数，最高的一位是符号位 S，接着的 8 位是指数 E，剩下的 23 位为有效数字 M
    //对于 64 位的浮点数，最高的一位是符号位 S，接着的 11 位是指数 E，剩下的 52 位为有效数字 M

    //一些其他的规定：
    //对于 M，总可以表示成 1.xxxxxx，因此第一位的 1 可以被舍去，只保留后面的 xxxxxx 部分，节省了一位有效数字
    //对于 E：规定 E 是一个无符号整数，如果 E 为 8 位，它的取值范围为 0 ~ 255，如果为 11 位，取值范围为 0 ~ 2047
    //在实际的科学计数法中，E 是有符号的，可以为负数，如何解决？

    //例：0.5 的二进制表示形式：0.1 - (-1)^0 * 1.0 * 2^(-1)
    //小数点后面的数不能直接按位转化，要和 0.5，0.25，0.125 ... 比较

    // IEEE754 规定，对存入内存的 E，必须加上一个中间值，对于 8 位的 E，这个中间数是 127；对于 11 位的 E，这个中间数是 1023
    //例：2^10 的 E = 10，存入的其实是 10 + 127 = 137 = 10001001

    {
        //举例：
        float f = 5.5F;
        // 5.5 --> 101.1 --> (-1)^0 * 1.011 * 2^2
        // S = 0
        // M = 1.011
        // E = 2 + 127 = 129 = 10000001
        // 0 10000001 01100000000000000000000
        // 0100 0000 1011 0000 0000 0000 0000 0000
        // 0x40 b0 00 00 --> 00 00 b0 40
        float* pf = &f;
        printf("%p\n", pf);
    }

    //当 E 从内存中取出的时候，也分为三种情况：
    // 1.E 不为全 0 或不为全 1 - 正常情况
    //此时取出的 E - 127 或 1023 就得到 E 的真实值，同时补全 M 前面的 1，按照公式计算还原数字
    // 2.E 为全 0 - 真实的 E 是 -127
    //有效数字 M 不再加上第一位的 1，而是还原为 0.xxxxxx 的小数，这样做是为了表示 ±0，以及接近于 0 的很小的数字
    //此时 E 被规定为 1 - 127 = -126
    // 0 00000000 01100000000000000000000 --> + 0.011 + 2^(-126)
    // 3.E 全为 1 - 真实的 E 是 128
    //这时，如果有效数字 M 全为 0，表示 ±无穷大(正负取决于符号位 s)

    {
        int n = 9;
        float* pFloat = (float*) &n;
        printf("n 的值为：%d\n", n);// 9
        printf("pFloat 的值为：%f\n", *pFloat);// 0.000000

        *pFloat = 9.0F;
        printf("n 的值为：%d\n", n);// 1091567616
        printf("pFloat 的值为：%f\n", *pFloat);// 9.000000
    }
    //解释：
    // 9 - 0000 0000 0000 0000 0000 0000 0000 1001
    //按浮点数分析 - 0 00000000 00000000000000000001001
    // 0.00000000000000000001001 * 2^(-126) --> 0.000000
    // 9.0 - 1001.0 - (-1)^0 * 1.001 * 2^3
    // 0 10000010 00100000000000000000000
    //按整数分析 - 1,091,567,616


    return 0;
}